<?php
declare(strict_types=1);

namespace ExpressionEngine\LexicalAnalysisEngine;

use Contract\Exceptions\LogicException;
use Contract\Exceptions\ValidationException;
use ExpressionEngine\ConstantEngine\ConstantEngineInterface;
use ExpressionEngine\LexicalAnalysisEngine\Enum\LexicalAnalysisEnum;
use ExpressionEngine\Utils\StringUtil;
use ExpressionEngine\VariableEngine\VariableEngineInterface;

class LexicalAnalysisSymbol
{
    protected LexicalAnalysisTag $lexicalAnalysisTag;
    protected VariableEngineInterface $variableEngine;
    protected ConstantEngineInterface $constantEngine;

    public function __construct(LexicalAnalysisTag $lexicalAnalysisTag, VariableEngineInterface $variableEngine, ConstantEngineInterface $constantEngine)
    {
        $this->lexicalAnalysisTag = $lexicalAnalysisTag;
        $this->variableEngine = $variableEngine;
        $this->constantEngine = $constantEngine;
    }

    /**
     * @param string $childExpression
     * @param array $symbolList
     * @return string
     * @throws LogicException
     * @throws ValidationException
     */
    public function scan(string $childExpression, array &$symbolList): string
    {
        $symbolList = $this->initializeSymbolList($symbolList);
        $length = strlen($childExpression);
        $symbol = '';
        $symbolStartIndex = 0;
        for ($i = 0; $i < $length; $i++) {
            if (strcmp($childExpression[$i], ' ') == 0) {
                continue;
            }
            if (empty($symbol)) {
                $symbolStartIndex = $i;
            }
            $symbol .= $childExpression[$i];

            if ($i + 1 == $length || strcmp($childExpression[$i + 1], ' ') == 0) {
                $symbolList = $this->replaceSymbol($childExpression, $symbolList, $symbolStartIndex, $symbol);
                $symbolStartIndex = 0;
                $symbol = '';
            }
        }
        $symbolList = $this->formatSymbolList($symbolList);
        return StringUtil::formatExpression($childExpression);
    }

    protected function initializeSymbolList(array $symbolList): array
    {
        if (!array_key_exists(LexicalAnalysisEnum::SCAN_KEY_VARIABLE_LIST_NAME, $symbolList)) {
            $symbolList[LexicalAnalysisEnum::SCAN_KEY_VARIABLE_LIST_NAME] = [];
        }
        if (!array_key_exists(LexicalAnalysisEnum::SCAN_KEY_CONSTANT_LIST_NAME, $symbolList)) {
            $symbolList[LexicalAnalysisEnum::SCAN_KEY_CONSTANT_LIST_NAME] = [];
        }
        return $symbolList;
    }

    /**
     * @param string $childExpression
     * @param array $symbolList
     * @param int $symbolStartIndex
     * @param string $symbol
     * @return array
     * @throws LogicException
     * @throws ValidationException
     */
    protected function replaceSymbol(string &$childExpression, array $symbolList, int $symbolStartIndex, string $symbol): array
    {
        if ($this->constantEngine->is($symbol)) {
            $constantId = count($symbolList[LexicalAnalysisEnum::SCAN_KEY_CONSTANT_LIST_NAME]);
            $symbolList[LexicalAnalysisEnum::SCAN_KEY_CONSTANT_LIST_NAME][$constantId] = $symbol;
            $childExpression = $this->lexicalAnalysisTag->replaceTag($childExpression,
                $symbolStartIndex, strlen($symbol), LexicalAnalysisEnum::TAG_ID_CONSTANT, $constantId);
        }
        if ($this->variableEngine->is($symbol)) {
            $variable = substr($symbol, 1, strlen($symbol) - 2);
            $symbolList = $this->replaceChildVariable($variable, $symbolList);
            $variableId = count($symbolList[LexicalAnalysisEnum::SCAN_KEY_VARIABLE_LIST_NAME]);
            $symbolList[LexicalAnalysisEnum::SCAN_KEY_VARIABLE_LIST_NAME][$variableId] = $variable;
            $childExpression = $this->lexicalAnalysisTag->replaceTag($childExpression,
                $symbolStartIndex, strlen($symbol), LexicalAnalysisEnum::TAG_ID_VARIABLE, $variableId);
        }
        return $symbolList;
    }

    /**
     * @param string $variable
     * @param array $symbolList
     * @return array
     * @throws LogicException
     * @throws ValidationException
     */
    protected function replaceChildVariable(string &$variable, array $symbolList): array
    {
        if (empty($variable)) {
            throw new ValidationException('variable is empty');
        }
        $length = strlen($variable);
        $stack = [];
        for ($i = 0; $i < $length; $i++) {
            if (strcmp($variable[$i], '{') == 0) {
                array_unshift($stack, $i);
            }
            if (strcmp($variable[$i], '}') == 0) {
                $variableId = count($symbolList[LexicalAnalysisEnum::SCAN_KEY_VARIABLE_LIST_NAME]);
                $childVariableStartIndex = array_shift($stack);
                $childVariableLength = $i - $childVariableStartIndex + 1;
                $symbolList[LexicalAnalysisEnum::SCAN_KEY_VARIABLE_LIST_NAME][$variableId] = substr($variable, $childVariableStartIndex + 1, $childVariableLength - 2);
                $variable = $this->lexicalAnalysisTag->replaceTag($variable, $childVariableStartIndex, $childVariableLength, LexicalAnalysisEnum::TAG_ID_VARIABLE, $variableId);
            }
        }
        if (count($stack) != 0) {
            throw new LogicException('花括号规则 不正确');
        }
        return $symbolList;
    }

    protected function formatSymbolList($symbolList): array
    {
        foreach ($symbolList as $name => $symbols) {
            foreach ($symbols as $key => $symbol) {
                $symbolList[$name][$key] = StringUtil::formatSymbol($symbol);
            }
        }
        return $symbolList;
    }
}